大家每天都是新的開始,都有24H小時給你規劃,系統跟人類一樣都是有自己的週期性計畫,人類需要休息進行處理其他工作,系統也是一樣需要有一段時間休息避開顛峰時間,故我們會先部分週期性的工作放在系統較不繁忙的時候去執行這些任務,此時我們已有許多週期性工作的套件,如:Quartz Scheduler、jcrontab或Fulcrum Scheduler等相關套件,而Spring 核心框架中以提供Spring Scheduling定期任務週期套件,無論是平均時間執行或定期時間點執行,以下將提供五種範例進行深入介紹與分析,讓我們深入看下去吧。
Spring Scheduled模組目的用於編製排程任務使用,開發者只要進行啟動排成註冊開關註解(@EnableScheduling),Spring Scheduled核心模組將會自動掃描專案內排成註解模式(@Scheduled),其註解支援的FixedRateTask、CronTask及FixedDelayTask三種排程性任務註解,第一項fixedRate,目的在於定期執行任務,即時下一次調度任務時,前項任務依舊在執行,仍然可以繼續調度任務。第二項fixedDelay,用於任務執行完時,等待再次執行的時間,並再次進行調度。第三項cron job,源於Unix OS一項實用性功能,可依照開發者所配置的年月日時間點進行執行該任務,其週期性可分為每年、每月、每周某幾日及每日進行調度其任務,可彈性化給每個開發者依照其任務性質進行調度觸發,小編以下提供四種範例給各位開發者作參考。
範例一、任務完成時,等待十秒鐘再次執行,這邊配置延遲觸發(initialDelay),意思在於容器啟動後,並延遲一秒在進行觸發任務。
@Scheduled(initialDelay=1000L,fixedDelay = 10000L)
public void triggerFixedDelaySeaFoodMethod () {
logger.info("triggerFixedDelaySeaFoodMethod scheduler");
SeaFood seaFood = new SeaFood()
.setId("C-0011")
.setDescription("Is a medium-sized burrowing crab that is named for its Busy claws.")
.setName("Busy crabs");
SEA_FOOD_CACHE_TAIWAN.asMap().putIfAbsent(seaFood.getId(),seaFood);
}
範例一、透過運作時間點可看出,確切在容器啟動後一秒後進行觸發,並在任務完成後每十秒觸發一次。
14:17:56.486 INFO 10383 --- [ main] sw.spring.sample.ApplicationBoot : Started ApplicationBoot in 2.632 seconds (JVM running for 3.118)
14:17:57.479 INFO 10383 --- [ scheduling-1] s.s.sample.scheduled.SeaFoodScheduler : triggerFixedDelaySeaFoodMethod scheduler
14:18:07.518 INFO 10383 --- [ scheduling-1] s.s.sample.scheduled.SeaFoodScheduler : triggerFixedDelaySeaFoodMethod scheduler
14:18:17.522 INFO 10383 --- [ scheduling-1] s.s.sample.scheduled.SeaFoodScheduler : triggerFixedDelaySeaFoodMethod scheduler
14:18:27.523 INFO 10383 --- [ scheduling-1] s.s.sample.scheduled.SeaFoodScheduler : triggerFixedDelaySeaFoodMethod scheduler
範例二、定期每十秒觸發一次任務。
@Scheduled(initialDelay=1000L,fixedRate=10000L)
public void triggerFixedRateSeaFoodMethod () {
logger.info("triggerFixedRateSeaFoodMethod scheduler");
SeaFood seaFood = new SeaFood()
.setId("C-0012")
.setDescription("Is a medium-sized burrowing crab that is named for its Bubby claws.")
.setName("Bubby crabs");
SEA_FOOD_CACHE_TAIWAN.asMap().putIfAbsent(seaFood.getId(),seaFood);
}
範例二、透過運作時間點可看出,確切在容器啟動後一秒後進行觸發,並每十秒觸發一次。
14:21:18.960 INFO 10399 --- [ main] sw.spring.sample.ApplicationBoot : Started ApplicationBoot in 3.017 seconds (JVM running for 3.479)
14:21:19.957 INFO 10399 --- [ scheduling-1] s.s.sample.scheduled.SeaFoodScheduler : triggerFixedRateSeaFoodMethod scheduler
14:21:29.961 INFO 10399 --- [ scheduling-1] s.s.sample.scheduled.SeaFoodScheduler : triggerFixedRateSeaFoodMethod scheduler
14:21:39.957 INFO 10399 --- [ scheduling-1] s.s.sample.scheduled.SeaFoodScheduler : triggerFixedRateSeaFoodMethod scheduler
14:21:49.974 INFO 10399 --- [ scheduling-1] s.s.sample.scheduled.SeaFoodScheduler : triggerFixedRateSeaFoodMethod scheduler
範例三、透過application.properties進行配置任務週期時間,並觸發其相關任務
@Scheduled(initialDelay = 1000L,fixedRateString="${sea.food.company.scheduled.fixedRate}")
public void triggerFixedRateSeaFoodMethodByConfig(){
logger.info("triggerFixedRateSeaFoodMethodByConfig scheduler");
SeaFood seaFood = new SeaFood()
.setId("F-0012")
.setDescription("Is a medium-sized burrowing crab that is named for its Shock Fish.")
.setName("Shock Fish");
SEA_FOOD_CACHE_TAIWAN.asMap().putIfAbsent(seaFood.getId(),seaFood);
}
@Scheduled(fixedDelayString ="${sea.food.company.scheduled.fixedDelay}")
public void triggerFixedDeleySeaFoodMethodByConfig () {
logger.info("triggerFixedDeleySeaFoodMethodByConfig scheduler");
SeaFood seaFood = new SeaFood()
.setId("F-0013")
.setDescription("Is a medium-sized burrowing crab that is named for its Baby Fish.")
.setName("Baby Fish");
SEA_FOOD_CACHE_TAIWAN.asMap().putIfAbsent(seaFood.getId(),seaFood);
}
範例三、運作結果可看出與上述結果一致
14:22:39.004 INFO 10410 --- [ main] sw.spring.sample.ApplicationBoot : Started ApplicationBoot in 2.346 seconds (JVM running for 2.799)
14:22:39.998 INFO 10410 --- [ scheduling-1] s.s.sample.scheduled.SeaFoodScheduler : triggerFixedRateSeaFoodMethodByConfig scheduler
14:22:49.019 INFO 10410 --- [ scheduling-1] s.s.sample.scheduled.SeaFoodScheduler : triggerFixedDeleySeaFoodMethodByConfig scheduler
14:22:50.000 INFO 10410 --- [ scheduling-1] s.s.sample.scheduled.SeaFoodScheduler : triggerFixedRateSeaFoodMethodByConfig scheduler
14:22:59.021 INFO 10410 --- [ scheduling-1] s.s.sample.scheduled.SeaFoodScheduler : triggerFixedDeleySeaFoodMethodByConfig scheduler
14:23:00.002 INFO 10410 --- [ scheduling-1] s.s.sample.scheduled.SeaFoodScheduler : triggerFixedRateSeaFoodMethodByConfig scheduler
14:23:09.022 INFO 10410 --- [ scheduling-1] s.s.sample.scheduled.SeaFoodScheduler : triggerFixedDeleySeaFoodMethodByConfig scheduler
14:23:09.999 INFO 10410 --- [ scheduling-1] s.s.sample.scheduled.SeaFoodScheduler : triggerFixedRateSeaFoodMethodByConfig scheduler
14:23:19.022 INFO 10410 --- [ scheduling-1] s.s.sample.scheduled.SeaFoodScheduler : triggerFixedDeleySeaFoodMethodByConfig scheduler
14:23:19.999 INFO 10410 --- [ scheduling-1] s.s.sample.scheduled.SeaFoodScheduler : triggerFixedRateSeaFoodMethodByConfig scheduler
範例四、每日定期觸發時間點配置
//application.properties配置
sea.food.company.scheduled.cron=0 30 14 * * ?
//配置Cron job
@Scheduled(cron="${sea.food.company.scheduled.cron}")
public void triggerCronJobByConfig () {
logger.info("triggerCronJobByConfig scheduler");
SeaFood seaFood = new SeaFood()
.setId("F-0014")
.setDescription("Is a medium-sized burrowing crab that is named for its HAHA Fish.")
.setName("HAHA Fish");
SEA_FOOD_CACHE_TAIWAN.asMap().putIfAbsent(seaFood.getId(),seaFood);
}
範例四、測試結果,以正確依照時間點進行觸發
14:26:28.643 INFO 10430 --- [ main] sw.spring.sample.ApplicationBoot : Started ApplicationBoot in 2.612 seconds (JVM running for 3.089)
14:30:00.007 INFO 10430 --- [ scheduling-1] s.s.sample.scheduled.SeaFoodScheduler : triggerCronJobByConfig scheduler
CRON 配置範例
"0 0 09 * * ?" 每天早上09:00觸發
"0 00 12 ? * *" 每天中午12:00觸發
"0 10 11 * * ?" 每天早上11:10觸發
"0 20 09 * * ? *" 每天早上09:20觸發
"0 11 11 * * ? 2021" 2021年的每天早上11:11觸發
"0 * 15 * * ?" 每天從15:00開始到15:59分每分鐘一次觸發
"0 0/10 15 * * ?" 每天從15:00開始到15:50分结束每10分鐘一次觸發
"0 0/10 14,18 * * ?" 每天的下午14:00至14:50和18:00至18:50
兩個時間内每5分鐘一次觸發
"0 0-3 08 * * ?" 每天08:00至08:03每分鐘一次觸發
"0 10,30 15 ? 3 MON" 三月的每周一的15:10和15:30觸發
"0 15 11 ? * MON-FRI" 每周一、周二、周三、周四、周五的11:15觸發
透過以上範例,各位開發者可依照實境情況進行配置,必定會有不同的效果。
前面章節已有詳述過,Spring @EnableXXXXX 所有功能都是透過ImportSelector進行實作,這邊的啟動開關@EnableScheduling一樣也是透過@Import進行引入SchedulingConfiguration模組,並透過ScheduledAnnotationBeanPostProcessor進行解析與處理每一個符合特定型別的Bean,並透過ScheduledTaskRegistrar將所有快取中的Task配置於任務排程器中執行。透過Scheduling中的TaskScheduler任務執行調度元件,可將執行任務調度器分為三種,一為ThreadPoolTaskScheduler,基於執行緒池實現的任務執行器,依賴底層ScheduledThreadPoolExecutor元件進行實現。二為ConcurrentTaskScheduler,用於自定義ScheduledThreadPoolExecutor型別Bean,任務執行器就會配置為此種任務調度器(ConcurrentTaskScheduler),三為DefaultManagedTaskScheduler,通過JNDI配置此排程執行器,依賴ScheduledThreadPoolExecutor實踐,一般較少用到,以下架構圖提供大家參考。
圖一 Scheduling 註解模組工作流程
Open Source Job Schedulers in Java
通過原始碼理解Spring中@Scheduled的實現原理並且實現排程任務動態裝載